﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Utilities;
using System.IO;
using Crude.Core;
using Crude.Common;
using System.Collections;
using Microsoft.Build.Framework;
using System.Reflection;
using Crude.Core.Repository;

namespace Crude.Tasks
{
	public class ResolveCrudeReferences : Task
	{
		[Required]
		public string ProjectDirectory { get; set; }

		[Required]
		public string ProjectName { get; set; }

		[Required]
		public string Configuration { get; set; }

		[Required]
		public ITaskItem[] ProjectReferences { get; set; }

		[Required]
		public string OutputDirectory { get; set; }

		[Output]
		public ITaskItem[] CrudeReferencePath { get; set; }

		[Output]
		public ITaskItem[] ResolvedDependencyFiles { get; set; }

		[Output]
		public ITaskItem[] RelatedFiles { get; set; }

		[Output]
		public ITaskItem[] CopyLocalFiles { get; set; }

		public override bool Execute()
		{
			string pomFile = string.Format("{0}\\{1}.pom.xml", ProjectDirectory, ProjectName);

			IList<ProjectObjectModel> assembliesToDownload = new List<ProjectObjectModel>();

			if (!File.Exists(pomFile))
			{
				Log.LogError("Pom file or project {0} is missing, please generate pom file {1}", ProjectName, pomFile);
				return false;
			}

			HashSet<string> projectReferencesNames = new HashSet<string>
					(from projectReference in ProjectReferences
					 select projectReference.GetMetadata("Name"));

			ProjectObjectModel mainPom = new ProjectObjectModelLoader(pomFile).Load();

			LocalRepository localRepository = new LocalRepositoryBuilder().Build();
			IRemoteRepository remoteRepository = new RemoteRepositoryBuilder().Build();

			IDictionary<string, ProjectObjectModel> solvedAssemblies = new Dictionary<string, ProjectObjectModel>();
			solvedAssemblies.Add(mainPom.Name, mainPom);

			Queue<ProjectDependency> dependencyToSolve = new Queue<ProjectDependency>();

			foreach (ProjectDependency dependency in mainPom.Dependencies)
			{
				dependencyToSolve.Enqueue(dependency);
			}

			while (dependencyToSolve.Count > 0)
			{
				ProjectDependency dependency = dependencyToSolve.Dequeue();

				if (!projectReferencesNames.Contains(dependency.Name))
				{
					ProjectObjectModel pom = localRepository.GetProjectObjectModel(dependency);

					if (pom == null)
					{
						pom = remoteRepository.GetProjectObjectModel(dependency);

						if (pom == null)
						{
							Log.LogError("Cannot find the dependency {0} version {1}",
									dependency.Name, dependency.Version);
							return false;
						}
						else if (!assembliesToDownload.Contains(pom))
						{
							assembliesToDownload.Add(pom);
						}
					}

					if (!solvedAssemblies.ContainsKey(pom.Name))
					{
						foreach (ProjectDependency subDependency in pom.Dependencies)
						{
							dependencyToSolve.Enqueue(subDependency);
						}

						solvedAssemblies.Add(pom.Name, pom);
					}
					else if (!solvedAssemblies[pom.Name].Version.Equals(pom.Version))
					{
						Log.LogError("Assemblies conflict {0} between version {1} to version {2}", pom.Name,
								pom.Version, solvedAssemblies[pom.Name].Version);
						return false;
					}
				}
			}

			solvedAssemblies.Remove(mainPom.Name);

			HashSet<string> mainProjectDependencies = new HashSet<string>(
																							 from projectDependency in mainPom.Dependencies
																							 select projectDependency.Name);

			foreach (ProjectObjectModel pom in assembliesToDownload)
			{
				Log.LogMessage("Downloading {0} version {1}", pom.Name, pom.Version);
				remoteRepository.DownloadAssmebly(pom);
			}

			Log.LogMessage("Complete downloading assemblies");

			IList<ITaskItem> crudeReferencesList = new List<ITaskItem>();
			IList<ITaskItem> resolvedDependencyFilesList = new List<ITaskItem>();
			IList<ITaskItem> relatedList = new List<ITaskItem>();
			IList<ITaskItem> copyLocalList = new List<ITaskItem>();

			foreach (ProjectObjectModel pom in solvedAssemblies.Values)
			{
				string pomDirectory = localRepository.GetPomDirectory(pom, Configuration);

				string mainFile = Path.Combine(ProjectDirectory, OutputDirectory, pom.MainOutputFile);

				string sourceFile = Path.Combine(pomDirectory, pom.MainOutputFile);

				AssemblyName assemblyName = AssemblyName.GetAssemblyName(sourceFile);
				

				TaskItem taskItem = new TaskItem(mainFile);
				taskItem.SetMetadata("ResolvedFrom", "{{CrudeRepository}}");
				taskItem.SetMetadata("CopyLocal", "true");
				taskItem.SetMetadata("FusionName", assemblyName.FullName);
				taskItem.SetMetadata("HintPath", mainFile);
				taskItem.SetMetadata("OriginalItemSpec", pom.Name);
				taskItem.SetMetadata("Version", pom.Version);

				TaskItem copyLocal = new TaskItem(sourceFile);
				copyLocalList.Add(copyLocal);

				if (mainProjectDependencies.Contains(pom.Name))
				{
					crudeReferencesList.Add(taskItem);
				}
				else
				{
					resolvedDependencyFilesList.Add(taskItem);
				}

				if (!File.Exists(mainFile))
				{
					File.Copy(sourceFile, mainFile);
				}

				foreach (OutputFile outputFile in pom.OutputFiles)
				{
					if (outputFile.FileName != pom.MainOutputFile)
					{
						TaskItem outputFileTaskItem = new TaskItem(pomDirectory + outputFile.FileName);

						relatedList.Add(outputFileTaskItem);
					}
				}
			}

			CrudeReferencePath = crudeReferencesList.ToArray();
			ResolvedDependencyFiles = resolvedDependencyFilesList.ToArray();
			RelatedFiles = relatedList.ToArray();
			//CopyLocalFiles = crudeReferencesList.Concat(
			//    resolvedDependencyFilesList).Concat(relatedList).ToArray();
			CopyLocalFiles = copyLocalList.ToArray();

			return true;
		}
	}
}
